package r25.board;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;

import r25.engine.Event;
import r25.engine.IEvent.EVENT;
import r25.engine.IGameInstance;
import r25.room.IRoom;
import r25.tools.LoggerHelper;
import r25.tools.PrintfFormat;

public abstract class AbstractBoard implements IBoard {
	private static final Logger logger = LoggerHelper.getLogger();
	private static final PrintfFormat pff = new PrintfFormat("%15s");
	private final Map<String, Object> parameters = new HashMap<String, Object>();
	private final List<ControlMove> validControlMoves = new ArrayList<ControlMove>();
	private IGameInstance gameInstance;

	protected IRoom[][] board;
	protected final List<IRoom> rooms = new ArrayList<IRoom>();

	@Override
	public void addRoom(IRoom r) throws Exception {
		if (getRoomAt(r.getPosX(), r.getPosY()) != null) {
			throw new Exception("Room location is not available on board: " + r);
		}
		this.rooms.add(r);
		setRoomAt(r, r.getPosX(), r.getPosY());
	}

	@Override
	public void setRoomAt(IRoom r, int posX, int posY) {
		board[posX - getLowerLimitX()][posY - getLowerLimitY()] = r;
	}

	@Override
	public IRoom getRoomAt(int posX, int posY) {
		return board[posX - getLowerLimitX()][posY - getLowerLimitY()];
	}

	@Override
	public IRoom getRoomAt(RoomPosition pos) {
		return getRoomAt(pos.getPosX(), pos.getPosY());
	}

	@Override
	public List<RoomPosition> getHiddenRoomPositions() {
		List<RoomPosition> rc = new ArrayList<RoomPosition>();
		for (IRoom r : rooms) {
			if (!r.isVisible()) {
				rc.add(new RoomPosition(r.getPosX(), r.getPosY()));
			}
		}
		return rc;
	}

	@Override
	public List<RoomPosition> getAdjacentRoomPositions(IRoom room,
			boolean checkAccessible) {
		List<RoomPosition> rc = new ArrayList<RoomPosition>();
		RoomPosition here = room.getRoomPosition();
		RoomPosition tested = null;

		if (here.getPosX() > getLowerLimitX()) {
			boolean ia = true;
			tested = new RoomPosition(here.getPosX() - 1, here.getPosY());
			if (checkAccessible) {
				ia = this.getRoomAt(tested).isAccessible();
			}
			if (ia)
				rc.add(tested);
		}
		if (here.getPosX() < getUpperLimitX()) {
			boolean ia = true;
			tested = new RoomPosition(here.getPosX() + 1, here.getPosY());
			if (checkAccessible) {
				ia = this.getRoomAt(tested).isAccessible();
			}
			if (ia)
				rc.add(tested);
		}
		if (here.getPosY() > getLowerLimitY()) {
			boolean ia = true;
			tested = new RoomPosition(here.getPosX(), here.getPosY() - 1);
			if (checkAccessible) {
				ia = this.getRoomAt(tested).isAccessible();
			}
			if (ia)
				rc.add(tested);
		}
		if (here.getPosY() < getUpperLimitY()) {
			boolean ia = true;
			tested = new RoomPosition(here.getPosX(), here.getPosY() + 1);
			if (checkAccessible) {
				ia = this.getRoomAt(tested).isAccessible();
			}
			if (ia)
				rc.add(tested);
		}
		return rc;
	}

	@Override
	public List<IRoom> getRoomsByName(String name) {
		List<IRoom> rc = new ArrayList<IRoom>();
		for (IRoom r : rooms) {
			if (r.getName().equals(name))
				rc.add(r);
		}
		return rc;
	}

	@Override
	public IRoom getRoomByName(String name) throws Exception {
		List<IRoom> rc = getRoomsByName(name);
		if (1 != rc.size()) {
			throw new Exception("Undefined or multiple Room: " + name);
		}
		return rc.get(0);
	}

	@Override
	public List<ControlMove> getValidControlMoves() {
		return validControlMoves;
	}

	@Override
	public List<ControlMove> getValidControlMoves(RoomPosition pos) {
		List<ControlMove> rc = new ArrayList<ControlMove>();
		for (ControlMove cm : validControlMoves) {
			if ((CONTROL_TYPE.COLUMN.equals(cm.getControlType()))
					&& (pos.getPosX() == cm.getPosition())) {
				rc.add(cm);
			}
			if ((CONTROL_TYPE.ROW.equals(cm.getControlType()))
					&& (pos.getPosY() == cm.getPosition())) {
				rc.add(cm);
			}
		}
		return rc;
	}

	@Override
	public void start(IGameInstance gi) throws Exception {
		this.gameInstance = gi;
		onNextTurn();
	}

	@Override
	public void controlRooms(ControlMove cm) throws Exception {
		controlRooms(cm.getPosition(), cm.getControlDirection(),
				cm.getControlType());
	}

	@Override
	public void controlRooms(int pos, CONTROL_DIRECTION cd, CONTROL_TYPE ct)
			throws Exception {
		ControlMove controlMove = new ControlMove(pos, cd, ct);
		logger.debug("Control {}", controlMove);

		if (CONTROL_TYPE.COLUMN.equals(ct)
				&& ((pos < getLowerLimitX()) || (pos > getUpperLimitX())))
			throw new Exception("Invalid position (" + ct + "): " + pos);
		if (CONTROL_TYPE.ROW.equals(ct)
				&& ((pos < getLowerLimitY()) || (pos > getUpperLimitY())))
			throw new Exception("Invalid position (" + ct + "): " + pos);
		if (!validControlMoves.contains(controlMove))
			throw new Exception("Invalid ControlMove: " + controlMove);

		if (CONTROL_TYPE.ROW.equals(ct)) {
			int posY = pos;

			// verify all Rooms are controllable
			boolean isControllable = true;
			for (int posX = getLowerLimitX(); posX <= getUpperLimitX(); posX++) {
				isControllable = isControllable
						&& getRoomAt(posX, posY).canBeControlled();
			}
			if (!isControllable)
				throw new Exception("Can not control " + ct + ": " + pos);

			// get the Room moving out of the Complex
			int outRoomPosX = (CONTROL_DIRECTION.POSITIVE.equals(cd)) ? getUpperLimitX()
					: getLowerLimitX();
			IRoom outRoom = getRoomAt(outRoomPosX, posY);

			// add event to the room moving out of the Complex
			if (gameInstance != null) {
				gameInstance.pushEvent(new Event(EVENT.ROOM_OUT_OF_COMPLEX,
						outRoom, null));
			}
			logger.debug("Moved out of the Complex: {}", outRoom);

			// cascade switch the other rooms
			if (CONTROL_DIRECTION.POSITIVE.equals(cd)) {
				for (int posX = getUpperLimitX() - 1; posX >= getLowerLimitX(); posX--) {
					switchRoomsAt(posX, posY, posX + 1, posY);
				}
			} else {
				for (int posX = getLowerLimitX() + 1; posX <= getUpperLimitX(); posX++) {
					switchRoomsAt(posX - 1, posY, posX, posY);
				}
			}
		} else {
			int posX = pos;

			// verify all Rooms are controllable
			boolean isControllable = true;
			for (int posY = getLowerLimitY(); posY <= getUpperLimitY(); posY++) {
				isControllable = isControllable
						&& getRoomAt(posX, posY).canBeControlled();
			}
			if (!isControllable)
				throw new Exception("Can not control " + ct + ": " + pos);

			// get the Room moving out of the Complex
			int outRoomPosY = (CONTROL_DIRECTION.POSITIVE.equals(cd)) ? getUpperLimitY()
					: getLowerLimitY();
			IRoom outRoom = getRoomAt(posX, outRoomPosY);

			// add event to the room moving out of the Complex
			if (gameInstance != null) {
				gameInstance.pushEvent(new Event(EVENT.ROOM_OUT_OF_COMPLEX,
						outRoom, null));
			}

			logger.debug("Moved out of the Complex: {}", outRoom);

			// cascade switch the other rooms
			if (CONTROL_DIRECTION.POSITIVE.equals(cd)) {
				for (int posY = getUpperLimitY() - 1; posY >= getLowerLimitY(); posY--) {
					switchRoomsAt(posX, posY, posX, posY + 1);
				}
			} else {
				for (int posY = getLowerLimitY() + 1; posY <= getUpperLimitY(); posY++) {
					switchRoomsAt(posX, posY - 1, posX, posY);
				}
			}
		}
		registerControlMove(controlMove);
	}

	private void registerControlMove(ControlMove cm) {
		ControlMove oppositeCm = new ControlMove(
				cm.getPosition(),
				(CONTROL_DIRECTION.POSITIVE.equals(cm.getControlDirection()) ? CONTROL_DIRECTION.NEGATIVE
						: CONTROL_DIRECTION.POSITIVE), cm.getControlType());
		validControlMoves.remove(oppositeCm);
	}

	@Override
	public void switchRoomsAt(int pos1x, int pos1y, int pos2x, int pos2y)
			throws Exception {
		if ((pos1x < getLowerLimitX()) || (pos1x > getUpperLimitX()))
			throw new Exception("Invalid position (x1): " + pos1x);
		if ((pos2x < getLowerLimitX()) || (pos2x > getUpperLimitX()))
			throw new Exception("Invalid position (x2): " + pos2x);
		if ((pos1y < getLowerLimitY()) || (pos1y > getUpperLimitY()))
			throw new Exception("Invalid position (y1): " + pos1y);
		if ((pos2y < getLowerLimitY()) || (pos2y > getUpperLimitY()))
			throw new Exception("Invalid position (y2): " + pos2y);

		IRoom r1 = getRoomAt(pos1x, pos1y);
		if ((r1 == null) || !r1.canBeSwitched())
			throw new Exception("Can not be switched: " + r1);
		IRoom r2 = getRoomAt(pos2x, pos2y);
		if ((r2 == null) || !r2.canBeSwitched())
			throw new Exception("Can not be switched: " + r2);

		if (r1.equals(r2))
			throw new Exception("Can not switch a room with itself: " + r1);

		r1.setPosX(pos2x);
		r1.setPosY(pos2y);

		r2.setPosX(pos1x);
		r2.setPosY(pos1y);

		setRoomAt(r1, r1.getPosX(), r1.getPosY());
		setRoomAt(r2, r2.getPosX(), r2.getPosY());
	}

	@Override
	public List<IRoom> getRooms() {
		return rooms;
	}

	@Override
	public void onNextTurn() {
		prepareValidControlMoves();
	}

	private void prepareValidControlMoves() {
		validControlMoves.clear();

		List<IRoom> nonControllableRooms = new ArrayList<IRoom>();
		for (IRoom r : rooms) {
			if (!r.canBeControlled()) {
				nonControllableRooms.add(r);
			}
		}

		for (int posX = getLowerLimitX(); posX <= getUpperLimitX(); posX++) {
			boolean canBeControlled = true;
			for (IRoom r : nonControllableRooms) {
				if (r.getPosX() == posX) {
					canBeControlled = false;
				}
			}

			if (canBeControlled) {
				validControlMoves.add(new ControlMove(posX,
						CONTROL_DIRECTION.POSITIVE, CONTROL_TYPE.COLUMN));
				validControlMoves.add(new ControlMove(posX,
						CONTROL_DIRECTION.NEGATIVE, CONTROL_TYPE.COLUMN));
			}
		}
		for (int posY = getLowerLimitY(); posY <= getUpperLimitY(); posY++) {
			boolean canBeControlled = true;
			for (IRoom r : nonControllableRooms) {
				if (r.getPosY() == posY) {
					canBeControlled = false;
				}
			}

			if (canBeControlled) {
				validControlMoves.add(new ControlMove(posY,
						CONTROL_DIRECTION.POSITIVE, CONTROL_TYPE.ROW));
				validControlMoves.add(new ControlMove(posY,
						CONTROL_DIRECTION.NEGATIVE, CONTROL_TYPE.ROW));
			}
		}
	}

	@Override
	public String toString(boolean showHidden) {
		StringBuffer rc = new StringBuffer();
		rc.append("Board ");
		rc.append(getSizeX());
		rc.append("x");
		rc.append(getSizeY());
		rc.append("\n");

		for (int y = getSizeY() - 1; y >= 0; y--) {
			for (int x = 0; x <= getSizeX() - 1; x++) {
				IRoom rxy = board[x][y];
				rc.append("[ ");
				String name = "EMPTY";
				if (rxy != null) {
					name = (showHidden) ? rxy.getName()
							: (rxy.isVisible()) ? rxy.getName() : "HIDDEN";
				}
				int posX = x + getLowerLimitX();
				int posY = y + getLowerLimitY();
				if (posX >= 0)
					rc.append(" ");
				rc.append(posX);
				rc.append(",");
				if (posY >= 0)
					rc.append(" ");
				rc.append(posY);
				rc.append(pff.sprintf(name));
				if (showHidden) {
					rc.append(":");
					rc.append(rxy.isVisible() ? "V" : "H");
				}
				rc.append(" ] ");
			}
			rc.append("\n");
		}
		return rc.toString();
	}

	@Override
	public String toString() {
		return toString(true);
	}

	@Override
	public void parameter(Map<String, Object> p) throws Exception {
		parameters.putAll(p);
	}
}
